package com.orange.core.service.writeoff;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.orange.core.mapper.floor.FloorMapper;
import com.orange.core.mapper.shop.ShopMapper;
import com.orange.core.mapper.writeoff.WriteOffMapper;
import com.orange.core.pojo.PageReqDto;
import com.orange.core.pojo.entity.employee.EmployeeCard;
import com.orange.core.pojo.entity.floor.Floor;
import com.orange.core.pojo.entity.order.CreditOrder;
import com.orange.core.pojo.entity.shop.Shop;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.entity.user.UserCoupon;
import com.orange.core.pojo.entity.writeoff.WriteOff;
import com.orange.core.pojo.enums.EUserType;
import com.orange.core.pojo.enums.EWriteOffStatus;
import com.orange.core.pojo.enums.EWriteOffType;
import com.orange.core.pojo.req.writeoff.WriteOffPageReq;
import com.orange.core.pojo.rsp.writeoff.*;
import com.orange.core.service.employee.EmployeeCardService;
import com.orange.core.service.order.CreditOrderService;
import com.orange.core.service.user.UserCouponService;
import com.orange.core.service.user.UserService;
import com.orange.core.util.Constants;
import com.orange.core.util.PrincipalUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class WriteOffService extends ServiceImpl<WriteOffMapper, WriteOff> {

    private static final String WRITE_OFF_LOCK = "write_off_lock:%s";

    private final UserService userService;
    private final UserCouponService userCouponService;
    private final CreditOrderService creditOrderService;
    private final EmployeeCardService employeeCardService;
    private final RedissonClient redissonClient;
    private final ShopMapper shopMapper;
    private final FloorMapper floorMapper;

    @SneakyThrows
    @Transactional
    public WriteOffResultRsp writeOff(String writeOffNo) {
        Assert.notBlank(writeOffNo, "核销码错误");

        User merchant = userService.getById(PrincipalUtil.getId());
        Assert.equals(merchant.getType(), EUserType.MERCHANT.getValue(), "只有商管才能核销");
        String prefix = writeOffNo.substring(0, 1);
        RLock lock = redissonClient.getFairLock(String.format(WRITE_OFF_LOCK, writeOffNo));
        boolean locked = false;
        try {
            lock.tryLock(5, 5, TimeUnit.SECONDS);
            locked = true;

            if (Objects.equals(prefix, Constants.COUPON_PREFIX)) {
                return userCouponService.writeOff(writeOffNo);
            } else if (Objects.equals(prefix, Constants.CREDIT_ORDER_PREFIX)) {
                return creditOrderService.writeOff(writeOffNo);
            } else {
                return employeeCardService.writeOff(writeOffNo);
            }
        } catch (IllegalStateException e) {
            log.error("加锁失败", e);
            throw e;
        } catch (Exception e) {
            log.error("方法执行失败", e);
            throw e;
        } finally {
            if (locked) {
                lock.unlock();
            }
        }
    }

    public WriteOffCountRsp writeOffCount() {
        Map<Integer, Long> writeOffCountMap = this.lambdaQuery()
                .select(WriteOff::getBusinessType)
                .eq(WriteOff::getWriteOffBy, PrincipalUtil.getId())
                .list()
                .stream().collect(Collectors.groupingBy(WriteOff::getBusinessType, Collectors.counting()));

        LocalDate today = LocalDate.now();
        Map<Integer, Long> writeOffCountTodayMap = this.lambdaQuery()
                .select(WriteOff::getBusinessType)
                .eq(WriteOff::getWriteOffBy, PrincipalUtil.getId())
                .eq(WriteOff::getWriteOffDate, today)
                .list()
                .stream().collect(Collectors.groupingBy(WriteOff::getBusinessType, Collectors.counting()));

        Map<Integer, Long> writeOffCountYesterdayMap = this.lambdaQuery()
                .select(WriteOff::getBusinessType)
                .eq(WriteOff::getWriteOffBy, PrincipalUtil.getId())
                .eq(WriteOff::getWriteOffDate, today.minusDays(1))
                .list()
                .stream().collect(Collectors.groupingBy(WriteOff::getBusinessType, Collectors.counting()));

        return WriteOffCountRsp.builder()
                .couponCount(writeOffCountMap.getOrDefault(EWriteOffType.COUPON.getValue(), 0L))
                .creditOrderCount(writeOffCountMap.getOrDefault(EWriteOffType.CREDIT_ORDER.getValue(), 0L))
                .employeeCount(writeOffCountMap.getOrDefault(EWriteOffType.EMPLOYEE_CARD.getValue(), 0L))
                .couponCountToday(writeOffCountTodayMap.getOrDefault(EWriteOffType.COUPON.getValue(), 0L))
                .creditOrderCountToday(writeOffCountTodayMap.getOrDefault(EWriteOffType.CREDIT_ORDER.getValue(), 0L))
                .couponCountYesterday(writeOffCountYesterdayMap.getOrDefault(EWriteOffType.COUPON.getValue(), 0L))
                .creditOrderCountYesterday(writeOffCountYesterdayMap.getOrDefault(EWriteOffType.CREDIT_ORDER.getValue(), 0L))
                .build();
    }

    public Page<WriteOffRsp> writeOffPage(PageReqDto<WriteOffPageReq> req) {
        if (req.getCondition() == null) {
            req.setCondition(new WriteOffPageReq());
        }
        WriteOffPageReq condition = req.getCondition();
        if (PrincipalUtil.isUser()) {
            User user = userService.getById(PrincipalUtil.getId());
            if (Objects.equals(user.getType(), EUserType.EMPLOYEE.getValue())) {
                condition.setEmployeeId(PrincipalUtil.getId());
            } else if (Objects.equals(user.getType(), EUserType.MERCHANT.getValue())) {
                if (Objects.equals(condition.getSource(), 1)) {
                    condition.setEmployeeId(PrincipalUtil.getId());
                } else {
                    condition.setWriteOffBy(PrincipalUtil.getId());
                }
            }
        }

        Page<WriteOff> page = this.lambdaQuery()
                .eq(condition.getShopId() != null, WriteOff::getShopId, condition.getShopId())
                .eq(condition.getEmployeeId() != null, WriteOff::getUserId, condition.getEmployeeId())
                .eq(condition.getType() != null, WriteOff::getBusinessType, condition.getType())
                .eq(condition.getWriteOffBy() != null, WriteOff::getWriteOffBy, condition.getWriteOffBy())
                .orderByDesc(WriteOff::getId)
                .page(req.toPage());
        List<WriteOff> writeOffs = page.getRecords();
        List<WriteOffRsp> results = new ArrayList<>();
        if (CollUtil.isNotEmpty(writeOffs)) {
            Map<Integer, List<Integer>> writeOffMap = writeOffs.stream()
                    .collect(Collectors.groupingBy(WriteOff::getBusinessType, Collectors.mapping(WriteOff::getBusinessId, Collectors.toList())));

            Map<Integer, UserCoupon> userCouponMap = new HashMap<>();
            Map<Integer, CreditOrder> creditOrderMap = new HashMap<>();
            Map<Integer, String> employeeCardMap = new HashMap<>();

            Map<Integer, Shop> shopMap = shopMapper.selectBatchIds(writeOffs.stream().map(WriteOff::getShopId).toList())
                    .stream().collect(Collectors.toMap(Shop::getId, Function.identity()));
            List<Integer> floorIds = shopMap.values().stream().map(Shop::getFloorId).toList();
            Map<Integer, String> floorMap = floorMapper.selectBatchIds(floorIds)
                    .stream().collect(Collectors.toMap(Floor::getId, Floor::getFloorName));

            List<Integer> userCouponIds = writeOffMap.get(EWriteOffType.COUPON.getValue());
            if (CollUtil.isNotEmpty(userCouponIds)) {
                userCouponMap = userCouponService.listByIds(userCouponIds)
                        .stream().collect(Collectors.toMap(UserCoupon::getId, Function.identity()));
            }
            List<Integer> creditOrderIds = writeOffMap.get(EWriteOffType.CREDIT_ORDER.getValue());
            if (CollUtil.isNotEmpty(creditOrderIds)) {
                creditOrderMap = creditOrderService.listByIds(creditOrderIds)
                        .stream().collect(Collectors.toMap(CreditOrder::getId, Function.identity()));
            }
            List<Integer> employeeCardIds = writeOffMap.get(EWriteOffType.EMPLOYEE_CARD.getValue());
            if (CollUtil.isNotEmpty(employeeCardIds)) {
                employeeCardMap = employeeCardService.listByIds(employeeCardIds)
                        .stream().collect(Collectors.toMap(EmployeeCard::getId, EmployeeCard::getCardNo));
            }

            for (WriteOff writeOff : writeOffs) {
                WriteOffRsp writeOffRsp;
                if (Objects.equals(writeOff.getBusinessType(), EWriteOffType.COUPON.getValue())) {
                    UserCoupon userCoupon = userCouponMap.get(writeOff.getBusinessId());
                    writeOffRsp = BeanUtil.copyProperties(userCoupon, CouponWriteOffRsp.class);
                    writeOffRsp.setOrderNo(userCoupon.getOrderNo());
                    writeOffRsp.setWriteOffType(EWriteOffType.COUPON.getValue());
                } else if (Objects.equals(writeOff.getBusinessType(), EWriteOffType.CREDIT_ORDER.getValue())) {
                    CreditOrder creditOrder = creditOrderMap.get(writeOff.getBusinessId());
                    writeOffRsp = BeanUtil.copyProperties(creditOrder, CreditOrderWriteOffRsp.class);
                    writeOffRsp.setOrderNo(creditOrder.getOrderNo());
                    writeOffRsp.setWriteOffType(EWriteOffType.CREDIT_ORDER.getValue());
                } else {
                    writeOffRsp = EmployeeWriteOffRsp.builder()
                            .employeeId(writeOff.getUserId())
                            .cardNo(employeeCardMap.get(writeOff.getUserId()))
                            .shopId(writeOff.getShopId())
                            .build();
                    writeOffRsp.setWriteOffType(EWriteOffType.EMPLOYEE_CARD.getValue());
                }
                writeOffRsp.setWriteOffTime(writeOff.getWriteOffTime());
                writeOffRsp.setWriteOffStatus(EWriteOffStatus.VERIFIED.getValue());
                Shop shop = shopMap.get(writeOff.getShopId());
                if (shop != null) {
                    writeOffRsp.setFloorId(shop.getFloorId());
                    writeOffRsp.setFloorName(floorMap.get(shop.getFloorId()));
                    writeOffRsp.setShopId(shop.getId());
                    writeOffRsp.setShopNumber(shop.getShopNumber());
                    writeOffRsp.setShopName(shop.getName());
                    writeOffRsp.setShopAddress(shop.getAddress());
                }
                results.add(writeOffRsp);
            }
        }
        return new Page<WriteOffRsp>(req.getPage(), req.getSize(), page.getTotal()).setRecords(results);
    }
}
